#include "convolution.h"

/** Overloaded constructor */
Convolution::Convolution(PNM* img) :
	Transformation(img)
{
}

Convolution::Convolution(PNM* img, ImageViewer* iv) :
	Transformation(img, iv)
{
}

/** Returns a convoluted form of the image */
PNM* Convolution::transform()
{
	return convolute(getMask(3, Normalize), RepeatEdge);
}

/** Returns a sizeXsize matrix with the center point equal 1.0 */
math::matrix<double> Convolution::getMask(int size, Mode mode = Normalize)
{
	math::matrix<double> mask(size, size);

	for (int i = 0; i < size; i++)
		for (int j = 0; j < size; j++) 
			mask(i, j) = 0.0;

	mask(size / 2, size / 2) = 1.0;

	return mask;
}

double Convolution::magic(int x, int y, int size, Transformation::Channel channel, Transformation::Mode mode, math::matrix<double> mask)
{
	double weight = sum(mask);
	auto window = getWindow(x, y, size, channel, mode);
	auto aku = join(window, mask);
	auto s = sum(aku);
	if(weight != 0) s /= weight;

	if(s < PIXEL_VAL_MIN) s = PIXEL_VAL_MIN;
	else if(s > PIXEL_VAL_MAX) s = PIXEL_VAL_MAX;

	return s;
}

/** Does the convolution process for all pixels using the given mask. */
PNM* Convolution::convolute(math::matrix<double> mask, Mode mode = RepeatEdge)
{
	int width  = image->width(),
		height = image->height();

	PNM* newImage = new PNM(width, height, image->format());

	double weight = sum(mask);
	int size = mask.RowNo();
	if (image->format() == QImage::Format_Indexed8)
	{
		for (int i = 0; i < width; i++)
			for (int j = 0; j < height; j++) 
			{
				double new_color = magic(i, j, size, Channel::LChannel, mode, mask);
				newImage->setPixel(i, j, new_color);
			}
	}
	else 
	{
		for (int i = 0; i < width; i++)
			for (int j = 0; j < height; j++) 
			{
				QColor new_pixel(
					magic(i, j, size, Channel::RChannel, mode, mask), 
					magic(i, j, size, Channel::GChannel, mode, mask), 
					magic(i, j, size, Channel::BChannel, mode, mask), 
					255);

				newImage->setPixel(i, j, new_pixel.rgb());
			}
	}
	return newImage;
}

/** Joins to matrices by multiplying the A[i,j] with B[i,j].
* Warning! Both Matrices must be squares with the same size!
*/
const math::matrix<double> Convolution::join(math::matrix<double> A, math::matrix<double> B)
{
	int size = A.RowNo();
	math::matrix<double> C(size, size);

	for (int i = 0; i < size; i++)
		for (int j = 0; j < size; j++) 
			C(i, j) = A(i, j) * B(i, j);

	return C;
}

/** Sums all of the matrixes elements */
const double Convolution::sum(const math::matrix<double> A)
{
	double sum = 0.0;

	int size = A.RowNo();

	for (int i = 0; i < size; i++)
		for (int j = 0; j < size; j++) 
			sum += A(i, j);

	return sum;

}
